package org.xtense.model.actions;

import java.lang.reflect.Method;
import java.util.List;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodProxy;

import org.dom4j.Attribute;
import org.dom4j.Element;
import org.dom4j.QName;
import org.dom4j.Text;
import org.dom4j.tree.DefaultElement;
import org.xtense.engine.actions.UpdateAction;

public class ElementInterceptor extends Interceptor {
	private static final String childAdded = "childAdded";
	private static final String childRemoved = "childRemoved";
	private static final String setName = "setName";
	private static final String setQName = "setQName";
	
	public ElementInterceptor() {
		transaction = false;
		enhancer = new Enhancer();
		enhancer.setCallback(this);
	}
	
	public Element createElement(String name) {
		enhancer.setSuperclass(DefaultElement.class);
		Object o = enhancer.create(new Class[] { String.class },
				new Object[] { name });

		return (Element) o;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Object intercept(Object o, Method method, Object[] args,
			MethodProxy proxy) throws Throwable {
		if (transaction) {
			if (childAdded.equals(method.getName())) {
				ElementInterceptor eleInter = new ElementInterceptor();
				AttributeInterceptor attInter = new AttributeInterceptor();
				
				if (args[0] instanceof Element) {
					// add the newly created element
					Element eordi = (Element) args[0];
					Element toAdd = eleInter.createElement(eordi.getName());
					
					Element base = (Element) o;
					
					// remove the added one
					this.setTransaction(false);
					int index = base.elements().indexOf(eordi);
					base.elements().remove(index);
					base.elements().add(index, toAdd);
					this.setTransaction(true);
					
					int docId = getDocIDFromNode(base);
					UpdateAction.getInstance(docId).getCache().cacheNode(eordi,
							toAdd);
					int elementPre;
					int parentPre;
					// get the correct elementPre and parentPre
					if (index == 0) {
						elementPre = getElementPreFromNode(docId, base);
						parentPre = getParentPreFromNode(docId, base);
					} else {
						
						base = (Element) base.elements().get(index - 1);
						elementPre = getElementPreFromNode(docId, base)
								+ getSize(base) - 1;
						parentPre = getParentPreFromNode(docId, base);
					}
					// update for free size
					int size = getSize(eordi);
					UpdateAction.getInstance(docId).getCache().cacheSql(
							UpdateHelper.getInstance()
							.updateForAdd(docId,
									elementPre, size));
					UpdateAction.getInstance(docId).updatePre(elementPre, size,
							true);
					
					String path = ResultCache.getPathFromNode(base);
					if(path == null) {
						path = UpdateAction.getInstance(docId).getCache()
								.getPath(base);
					}
					// go through the child elements and attribute
					eordi.accept(new AddVisitor(eleInter, attInter, docId,
							elementPre, parentPre, path));
					
					
				} else if (args[0] instanceof Attribute) {
					// created a new attribute by us
					Attribute eordi = (Attribute) args[0];
					Attribute toAdd = attInter.createAttribute(eordi.getName(),
							eordi.getValue());
					
					Element base = (Element) o;
					
					if (base.attributeValue(eordi.getName()) != null) {
						// remove the added one
						this.setTransaction(false);
						int index = base.attributes().indexOf(eordi);
						base.attributes().remove(index);
						base.attributes().add(index, toAdd);
						this.setTransaction(true);

						int docId = getDocIDFromNode(base);

						int elementPre = getElementPreFromNode(docId, base);

						UpdateAction.getInstance(docId).getCache().cacheSql(
								UpdateHelper.getInstance().addAttribute(docId,
										elementPre, eordi.getName(),
										eordi.getValue()));
					}
					
				} else if (args[0] instanceof Text) {

					Text eordi = (Text) args[0];
					
					Element base = (Element) o;
					
					int docId = getDocIDFromNode(base);

					int elementPre = getElementPreFromNode(docId, base);
					
					if (base.getTextTrim().length() > 0) {
						// have text -> change
						UpdateAction.getInstance(docId).getCache().cacheSql(
								UpdateHelper.getInstance().changeTextContent(
										docId, elementPre, eordi.getText()));
					} else {
						// have no text -> set
						UpdateAction.getInstance(docId).getCache().cacheSql(
							UpdateHelper.getInstance().addText(docId,
									elementPre, eordi.getText()));
					}					
					
				} 
				
				eleInter.setTransaction(true);
				attInter.setTransaction(true);
			} else if (childRemoved.equals(method.getName())) {

				if (args[0] instanceof Element) {
					Element eorig = (Element) args[0];
					int docId = getDocIDFromNode(eorig);
					
					int elementPre = getElementPreFromNode(docId, eorig)
							+ getSize(eorig) - 1;
					
					eorig.accept(new RemoveVisitor(docId));
					
					int size = getSize(eorig);
					UpdateAction.getInstance(docId).getCache().cacheSql(
							UpdateHelper.getInstance().updateForDelete(docId,
									elementPre, size));
					UpdateAction.getInstance(docId).updatePre(elementPre, size,
							false);
					
				} else if (args[0] instanceof Attribute) {
					Attribute att = (Attribute) args[0];
					Element e = att.getParent();
					int docId = getDocIDFromNode(e);
					int elementPre = getElementPreFromNode(docId, e);
					
					UpdateAction.getInstance(docId).getCache().cacheSql(
							UpdateHelper.getInstance().removeAttribute(docId,
									elementPre, att.getName()));
					
				} else if (args[0] instanceof Text) {
					Text text = (Text) args[0];
					Element e = text.getParent();
					int docId = getDocIDFromNode(e);
					int elementPre = getElementPreFromNode(docId, e);

					UpdateAction.getInstance(docId).getCache().cacheSql(
							UpdateHelper.getInstance().removeText(docId,
									elementPre));
				}

			} else if (setName.equals(method.getName())) {
	
				Element base = (Element) o;
				int docId = getDocIDFromNode(base);
				int elementPre = getElementPreFromNode(docId, base);
				
				UpdateAction.getInstance(docId).getCache().cacheSql(
						UpdateHelper.getInstance().changeElementName(docId,
								elementPre, args[0].toString()));
			} else if (setQName.equals(method.getName())) {

				Element base = (Element) o;
				QName qName = (QName) args[0];
				int docId = getDocIDFromNode(base);
				int elementPre = getElementPreFromNode(docId, base);
				UpdateAction.getInstance(docId).getCache().cacheSql(
						UpdateHelper.getInstance().changeElementName(docId,
								elementPre, qName.getName()));
			}
		}
		return proxy.invokeSuper(o, args);
	}
	
	@SuppressWarnings("unchecked")
	private int getSize(Element element) {
		if (element.elements().size() == 0) {
			return 1;
		} else {
			int sum = 1;
			for (Element e : (List<Element>) element.elements()) {
				sum += getSize(e);
			}
			return sum;
		}
	}

}
